﻿var Constants = {
    Mode: {
        REVIEW: 0,
        UPDATE: 1,
        VIEW: 2
    }
};

$(document).ready(function () {
    var i = 1;
    $(document).on("focus", "[contenteditable]", function () {
        var $this = $(this);
        $this.data("before-blur", $this.html()).data('before-keyup', $this.html());
        return $this;
    }).on("blur", "[contenteditable]", function () {
        var $this = $(this);
        if ($this.data('before-blur') !== $this.html()) {
            $this.trigger('change');
        }
        return $this;
    }).on("keyup", "[contenteditable]", function () {
        var $this = $(this);
        if ($this.data('before-keyup') !== $this.html()) {
            $this.data('before-keyup', $this.html());
            $this.trigger('changing');
        }
        return $this;
    });

    ko.bindingHandlers.contenteditable = {
        init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
            var value = ko.utils.unwrapObservable(valueAccessor());

            $(element).html(value).attr('contenteditable', 'true').change(function () {
                valueAccessor()($(this).html());
            }).on('changing', function () {
                viewModel.dialog.updatePos();
            });
        },
        update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
            ko.bindingHandlers.html.update(element, valueAccessor);
        }
    };
    ko.bindingHandlers.code = {
        init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
        },
        update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
            ko.bindingHandlers.text.update(element, valueAccessor);
        }
    };
    ko.bindingHandlers.comment = {
        init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
            var value = ko.utils.unwrapObservable(valueAccessor());

            var comment = $(element).parent().commentDialog(value, {
                direction: 'right',
                container: $('#code')
            });
            value.dialog = comment;
        },
        update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
        }
    };

    $.fn.commentDialog = function (data, options) {
        return new CommentDialog(this, data, options).init();
    };
    function CommentDialog(element, data, options) {
        var self = this;

        this.element = element;
        this.data = data;
        this.options = options;

        this.focus = function (mode) {
            var problem = this.element.find('.problem');
            var response = this.element.find('.response');
            var isFocused = false;
            isFocused |= problem.hasAttr('contenteditable') && problem.is(':focus');
            isFocused |= response.hasAttr('contenteditable') && response.is(':focus');

            if (!isFocused) {
                switch (mode) {
                    case Constants.Mode.VIEW:
                        break;
                    case Constants.Mode.REVIEW:
                        problem.focus();
                        break;
                    case Constants.Mode.UPDATE:
                        response.focus();
                        break;
                }
            }
        };
        this.remove = function () {
            self.mark.replaceWith(self.mark.contents());
            self.element.remove();
            return self;
        };
        this.init = function () {
            var container = options.container;
            var code = options.container.find('pre');
            code.reindex();

            var range = code.getRange(this.data.offset(), this.data.length());
            this.mark = $('comment', this.element);
            range.surroundContents(this.mark[0]);

            this.arrow = $('<img>').css({ position: 'absolute' }).appendTo(this.element);
            this.updatePos();

            $(window).resize(function () {
                self.updatePos();
            });
            return this;
        };
        this.updatePos = function () {
            var container = options.container;

            var arrowSize = {
                width: 8,
                height: 14
            };

            var bound = {
                left: container.scrollLeft() + this.mark.position().left,
                top: container.scrollTop() + this.mark.position().top,
                width: this.mark.outerWidth(),
                height: this.mark.outerHeight()
            };

            var epos = {
                left: 0,
                top: 0
            };

            switch (this.options.direction) {
                case 'left':
                    epos.left = bound.left - this.element.outerWidth() - arrowSize.width;
                    epos.top = bound.top + bound.height / 2 - this.element.outerHeight() / 2;
                    break;
                case 'right':
                    epos.left = bound.left + bound.width + arrowSize.width;
                    epos.top = bound.top + bound.height / 2 - this.element.outerHeight() / 2;
                    break;
                case 'top':
                    epos.left = bound.left + bound.width / 2 - this.element.outerWidth() / 2;
                    epos.top = bound.top - this.element.outerHeight() - arrowSize.width;
                    break;
                case 'bottom':
                    epos.left = bound.left + bound.width / 2 - this.element.outerWidth() / 2;
                    epos.top = bound.top + bound.height + arrowSize.width;
                    break;
            }
            if (this.options.direction != 'left' && epos.left < 2) {
                epos.left = 2;
            }
            if (this.options.diretion != 'top' && epos.top < 2) {
                epos.top = 2;
            }
            this.element.css({
                position: 'absolute',
                left: epos.left,
                top: epos.top
            });

            var apos = { left: 0, top: 0 };
            switch (this.options.direction) {
                case 'left':
                    apos.left = this.element.width();
                    apos.top = bound.top + bound.height / 2 - epos.top - arrowSize.height / 2;
                    break;
                case 'right':
                    apos.left = -arrowSize.width;
                    apos.top = bound.top + bound.height / 2 - epos.top - arrowSize.height / 2;
                    break;
                case 'top':
                    apos.left = bound.left + bound.width / 2 - epos.left - arrowSize.height / 2;
                    apos.top = this.element.height();
                    break;
                case 'bottom':
                    apos.left = bound.left + bound.width / 2 - epos.left - arrowSize.height / 2;
                    apos.top = -arrowSize.width;
                    break;
            }
            this.arrow.css({
                top: apos.top,
                left: apos.left
            }).attr({
                src: 'images/white-arrow-' + this.options.direction + '.png'
            });

            return this;
        };
    }

    $.fn.reindex = function (index) {
        if (!index) {
            index = 0;
        }
        this.contents().each(function () {
            $(this).data({
                offset: index,
                length: $(this).text().length
            });
            if (this.nodeType != Node.TEXT_NODE) {
                $(this).reindex(index);
            }
            index += $(this).text().length;
        });
    };
    $.fn.getLocation = function (offset) {
        var contents = $(this).contents();
        for (var i = 0; i < contents.length; i++) {
            var o = $(contents[i]).data('offset');
            var l = $(contents[i]).data('length');

            if (o <= offset && o + l >= offset) {
                if (contents[i].nodeType == Node.TEXT_NODE) {
                    return {
                        node: contents[i],
                        offset: offset - o
                    };
                }
                else {
                    return $(contents[i]).getLocation(offset);
                }
            }
        }
    };
    $.fn.getRange = function (offset, length) {
        var start = $(this).getLocation(offset);
        var end = $(this).getLocation(offset + length);

        var range = document.createRange();
        range.setStart(start.node, start.offset);
        range.setEnd(end.node, end.offset);
        return range;
    };

    function Comment() {
        var self = this;

        this.review = null;
        this.dialog = null;

        this.offset = ko.observable();
        this.length = ko.observable();
        this.location = ko.observable();
        this.code = ko.observable();

        this.problem = ko.observable();
        this.response = ko.observable();

        this.datetime = ko.observable();

        this.init = function () {
            self.offset(0);
            self.length(0);
            self.code('');
            self.problem('');
            self.response('');
            self.datetime('');
        };
        this.remove = function () {
            self.dialog.remove();
            self.review.comments.remove(self);
        };
        this.select = function () {
            self.review.selectedComment(self);
            self.dialog.focus(self.review.mode());
        };
        this.isSelected = function () {
            return self == self.review.selectedComment();
        };
        this.import = function (data) {
            for (var key in data) {
                this[key](data[key]);
            }
        };
        this.export = function () {
            return {
                offset: self.offset(),
                length: self.length(),
                code: self.code(),
                problem: self.problem(),
                response: self.response(),
                datetime: self.datetime()
            };
        };
    }

    function Review() {
        var self = this;

        this.mode = ko.observable(Constants.Mode.REVIEW);

        this.filename = ko.observable();
        this.codefile = ko.observable();
        this.creator = ko.observable();
        this.reviewer = ko.observable();
        this.reviewDate = ko.observable();
        this.comments = ko.observableArray();
        this.selectedComment = ko.observable();

        this.code = ko.observable();
        this.linecount = ko.observable();
        this.linenums = ko.observable();

        var locked = false;
        var i = 0;
        $(window).keydown(function (e) {
            if (!$(document.activeElement).hasAttr('contenteditable')) {
                switch (e.keyCode) {
                    case 32:
                        e.preventDefault();
                        self.addComment();
                        break;
                }
            }
        });

        this.getFilename = function () {
            return 'file';
        };

        this.select = function (comment) {
            this.selectedComment(comment);
        };
        this.init = function () {
            self.filename('');
            self.codefile('');
            self.creator('');
            self.reviewer('');
            self.reviewDate('');
            self.comments([]);
            self.selectedComment(null);
            self.code('');
            self.linecount(0);
            self.linenums('');
        };
        this.open = function (item, e) {
            $(e.target).guard(function () {
                Local.ajax('open', self.filename(), function (data) {
                    if (data) {
                        self.init();
                        self.filename(data.filename);
                        self.import(JSON.parse(data.review));

                        $(window).trigger('resize');
                    }
                }, self);
            });
        };
        this.save = function (item, e) {
            $(e.target).guard(function () {
                Local.ajax('save', { filename: self.filename(), review: JSON.stringify(self.export()) }, function (data) {
                    if (data) {
                        self.filename(data);
                    }
                }, self);
            });
        };
        this.import = function (data) {
            for (var key in data) {
                switch (key) {
                    case 'comments':
                        var comments = [];
                        for (var i = 0; i < data[key].length; i++) {
                            var comment = new Comment();
                            comment.review = self;
                            comment.import(data[key][i]);
                            comments.push(comment);
                        }
                        self.comments(comments);
                        break;
                    case 'code':
                        self[key](data[key]);
                        self.linecount(self.code().split('\n').length);
                        var linenums = '';
                        for (var i = 1; i < self.linecount(); i++) {
                            linenums += i + '\n';
                        }
                        linenums += '\n\n\n';
                        self.linenums(linenums);
                        break;
                    default:
                        self[key](data[key]);
                        break;
                }
            }
        };
        this.export = function () {
            var data = {
                codefile: self.codefile(),
                creator: self.creator(),
                reviewer: self.reviewer(),
                reviewDate: self.reviewDate(),
                code: self.code(),
                comments: []
            };
            var comments = self.comments();
            for (var i = 0; i < comments.length; i++) {
                try {
                    data.comments.push(comments[i].export());
                }
                catch (err) {
                    window.external.Alert(err);
                }
            }
            return data;
        };
        this.addComment = function (item, e) {
            $('#code pre').reindex();

            var selection = document.getSelection();
            var range = selection.getRangeAt(0);
            var root = $('#code');
            if (range.toString().length <= 0 || !$(range.startContainer).hasParent(root) || !$(range.endContainer).hasParent(root) ||
                $(range.startContainer).hasParent("comment") || $(range.endContainer).hasParent("comment")) {
                return;
            }

            var comment = new Comment();
            comment.review = self;
            comment.offset($(range.startContainer).data('offset') + range.startOffset);
            comment.length(range.toString().length);
            comment.code(range.toString());
            comment.problem('&nbsp;');
            comment.datetime(new Date().format('yyyy/MM/dd HH:mm:ss'));

            self.comments.push(comment);
            comment.select();
        };
    }

    var review = new Review();

    /*
    review.filename('c:\\temp\\test.rvw');
    review.codefile('c:\\temp\\test.cs');
    review.creator('AnhTT');
    review.reviewer('TuanMA');
    review.reviewDate('2012/06/14');
    review.code('imports System;\r\nimports System;\r\nimports System;\r\nimports System;\r\nimports System;\r\nimports System;\r\nimports System;\r\nimports System;\r\nimports System;imports System;imports System;imports System;imports System;imports System;imports System;imports System;imports System;imports System;imports System;imports System;imports System;imports System;imports System;imports System;imports System;imports System;imports System;imports System;imports System;imports System;imports System;imports System;imports System;imports System;imports System;imports System;\r\nimports System;\r\nimports System;\r\nimports System;\r\nimports System;\r\nimports System;\r\nimports System;\r\nimports System;\r\nimports System;\r\nimports System;\r\nimports System;\r\nimports System;\r\nimports System;\r\nimports System;\r\nimports System;\r\nimports System;\r\nimports System;\r\nimports System;\r\nimports System;\r\nimports System;\r\nimports System;\r\nimports System;\r\nimports System;\r\nimports System;\r\nimports System;\r\nimports System;\r\nimports System;\r\nimports System;\r\n...');
    review.linenums('1\n2\n3\n4\n5\n6\n7\n8\n9\n10');
    
    for (var i = 0; i < 20; i++) {
    var comment = new Comment();
    comment.offset(Math.floor(Math.random() * (review.code().length - 20)));
    comment.length(Math.floor(Math.random() * 10 + 1));
    comment.code(review.code().substring(comment.offset(), comment.offset()+comment.length()));
    comment.location(i);
    comment.problem('Nên sửa thành...');
    comment.response('');
    comment.datetime(new Date().format('yyyy/MM/dd HH:mm:ss'));

    review.comments.push(comment);
    }
    */

    $(window).resize(function () {
        $('.vscroll').each(function () {
            $(this).width($(this).parent().width());
        });
        $('#code').trigger('scroll');
    });
    $('#code').scroll(function () {
        $('#linenums').scrollTop($(this).scrollTop());
    });

    ko.applyBindings(review);
});

$(window).load(function () {
    $(window).trigger('resize');
});